package com.tmt.datatable.editor;

import com.tmt.datatable.editor.impl.DefaultFieldValueSetter;
import com.tmt.datatable.model.FieldError;
import org.springframework.context.MessageSource;
import org.springframework.core.MethodParameter;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.support.AbstractMultipartHttpServletRequest;

import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * Editor Model 参数解析
 * Created by Stark on 2017/5/10.
 */
public class EditorModelArgumentResolver implements HandlerMethodArgumentResolver {

    private MessageSource messageSource;

    private IFileDataHolder fileDataHolder;

    private IFieldValueSetter fieldValueSetter;

    private IUploadActionHandler uploadActionHandler;

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return Objects.equals(parameter.getParameterType(), EditorModel.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
        ParameterizedType parameterizedType = (ParameterizedType) parameter.getGenericParameterType();
        Type modelClassType = parameterizedType.getActualTypeArguments()[0];
        Class<?> modelClass = Class.forName(modelClassType.getTypeName());

        IFileDataHolder clonedHolder = null;
        String[] fieldNames = request.getParameterMap().get("uploadField");
        if (fieldNames != null && fieldNames.length > 0) {
            Map<String, MultipartFile> fileMap = ((AbstractMultipartHttpServletRequest) request).getFileMap();
            if (fileDataHolder != null) {
                clonedHolder = fileDataHolder.newInstance();
                //重新更新fileDataHolder的属性值。
                clonedHolder.setFieldName(fieldNames[0]);
                clonedHolder.setFileData(fileMap.values().toArray()[0]);
            }
        }

        if (fieldValueSetter == null) {
            fieldValueSetter = new DefaultFieldValueSetter();
        }

        EditorModel model = EditorModel.from(modelClass, request.getParameterMap(), clonedHolder,
                fieldValueSetter, uploadActionHandler);

        if (binderFactory != null) {
            boolean shouldValid = parameter.hasParameterAnnotation(Valid.class);
            EditorValid editorValid = parameter.getParameterAnnotation(EditorValid.class);
            if (shouldValid && editorValid != null) {

                String validtorName = editorValid.value();
                if ("".equals(validtorName.trim()))
                    validtorName = modelClass.getSimpleName();

                List<?> listDate = model.getData();
                for (int i = 0, j = listDate.size(); i < j; i++) {
                    WebDataBinder binder = binderFactory.createBinder(webRequest, model.getData().get(i), validtorName);
                    if (binder == null)
                        binder = binderFactory.createBinder(webRequest, model.getData().get(i), "");

                    if (binder.getTarget() != null) {
                        binder.validate();

                        BindingResult result = binder.getBindingResult();
                        if (result != null && result.hasErrors()) {
                            model.setFieldErrors(convertErrors(result.getAllErrors()));
                        }

                        Map<String, Object> bindingResultModel = binder.getBindingResult().getModel();
                        mavContainer.removeAttributes(bindingResultModel);
                        mavContainer.addAllAttributes(bindingResultModel);
                    }
                }
            }
        }

        return model;
    }

    /**
     * 将 spring 的错误信息转换为 EditorModel 的内部错误信息
     */
    private List<FieldError> convertErrors(List<ObjectError> errors) {
        return errors.stream().map(e -> {
            org.springframework.validation.FieldError fieldError = (org.springframework.validation.FieldError) e;
            String msg = StringUtils.isEmpty(fieldError.getCode())
                    ? fieldError.getDefaultMessage()
                    : messageSource.getMessage(fieldError.getCode(), fieldError.getArguments(), Locale.getDefault());
            return new FieldError(fieldError.getField(), msg);
        }).collect(Collectors.toList());
    }

    public void setMessageSource(MessageSource messageSource) {
        this.messageSource = messageSource;
        if (uploadActionHandler != null)
            uploadActionHandler.setMessageSource(messageSource);
    }

    public void setFileDataHolder(IFileDataHolder fileDataHolder) {
        this.fileDataHolder = fileDataHolder;
    }

    public void setFieldValueSetter(IFieldValueSetter fieldValueSetter) {
        this.fieldValueSetter = fieldValueSetter;
    }

    public void setUploadActionHandler(IUploadActionHandler uploadActionHandler) {
        this.uploadActionHandler = uploadActionHandler;
        if (messageSource != null)
            this.uploadActionHandler.setMessageSource(messageSource);
    }
}
